Libérez la puissance des aides pour générateurs asynchrones JavaScript pour la création, transformation et gestion efficaces de flux. Explorez des exemples pratiques pour construire des applications asynchrones robustes.
Aides pour Générateurs Asynchrones JavaScript : Maîtriser la Création et la Gestion de Flux
La programmation asynchrone en JavaScript a considérablement évolué au fil des ans. Avec l'introduction des générateurs asynchrones et des itérateurs asynchrones, les développeurs ont obtenu des outils puissants pour gérer les flux de données asynchrones. Aujourd'hui, les aides pour générateurs asynchrones JavaScript améliorent encore ces capacités, offrant un moyen plus rationalisé et expressif de créer, transformer et gérer les flux de données asynchrones. Ce guide explore les principes fondamentaux des aides pour générateurs asynchrones, examine leurs fonctionnalités et démontre leurs applications pratiques avec des exemples clairs.
Comprendre les Générateurs et Itérateurs Asynchrones
Avant de plonger dans les aides pour générateurs asynchrones, il est crucial de comprendre les concepts sous-jacents des générateurs asynchrones et des itérateurs asynchrones.
Générateurs Asynchrones
Un générateur asynchrone est une fonction qui peut être mise en pause et reprise, produisant des valeurs de manière asynchrone. Il vous permet de générer une séquence de valeurs au fil du temps, sans bloquer le thread principal. Les générateurs asynchrones sont définis à l'aide de la syntaxe async function*.
Exemple :
async function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 500)); // Simule une opération asynchrone
yield i;
}
}
// Utilisation
const sequence = generateSequence(1, 5);
Itérateurs Asynchrones
Un itérateur asynchrone est un objet qui fournit une méthode next(), laquelle retourne une promesse qui se résout en un objet contenant la prochaine valeur de la séquence et une propriété done indiquant si la séquence est terminée. Les itérateurs asynchrones sont consommés à l'aide de boucles for await...of.
Exemple :
async function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 500));
yield i;
}
}
async function consumeSequence() {
const sequence = generateSequence(1, 5);
for await (const value of sequence) {
console.log(value);
}
}
consumeSequence();
Présentation des Aides pour Générateurs Asynchrones
Les aides pour générateurs asynchrones sont un ensemble de méthodes qui étendent les fonctionnalités des prototypes de générateurs asynchrones. Elles fournissent des moyens pratiques de manipuler les flux de données asynchrones, rendant le code plus lisible et maintenable. Ces aides fonctionnent de manière paresseuse, ce qui signifie qu'elles ne traitent les données que lorsque c'est nécessaire, ce qui peut améliorer les performances.
Les aides pour générateurs asynchrones suivantes sont couramment disponibles (selon l'environnement JavaScript et les polyfills) :
mapfiltertakedropflatMapreducetoArrayforEach
Exploration Détaillée des Aides pour Générateurs Asynchrones
1. `map()`
L'aide map() transforme chaque valeur de la séquence asynchrone en appliquant une fonction fournie. Elle retourne un nouveau générateur asynchrone qui produit les valeurs transformées.
Syntaxe :
asyncGenerator.map(callback)
Exemple : Convertir un flux de nombres en leurs carrés.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
const squares = numbers.map(async (num) => {
await new Promise(resolve => setTimeout(resolve, 100)); // Simule une opération asynchrone
return num * num;
});
for await (const square of squares) {
console.log(square);
}
}
processNumbers();
Cas d'usage réel : Imaginez récupérer des données utilisateur depuis plusieurs API et devoir transformer ces données dans un format cohérent. map() peut être utilisé pour appliquer une fonction de transformation à chaque objet utilisateur de manière asynchrone.
async function* fetchUsersFromMultipleAPIs(apiEndpoints) {
for (const endpoint of apiEndpoints) {
const response = await fetch(endpoint);
const data = await response.json();
for (const user of data) {
yield user;
}
}
}
async function processUsers() {
const apiEndpoints = [
'https://api.example.com/users1',
'https://api.example.com/users2'
];
const users = fetchUsersFromMultipleAPIs(apiEndpoints);
const normalizedUsers = users.map(async (user) => {
// Normaliser le format des données utilisateur
return {
id: user.userId || user.id,
name: user.fullName || user.name,
email: user.emailAddress || user.email
};
});
for await (const normalizedUser of normalizedUsers) {
console.log(normalizedUser);
}
}
2. `filter()`
L'aide filter() crée un nouveau générateur asynchrone qui ne produit que les valeurs de la séquence originale qui satisfont une condition fournie. Elle vous permet d'inclure sélectivement des valeurs dans le flux résultant.
Syntaxe :
asyncGenerator.filter(callback)
Exemple : Filtrer un flux de nombres pour n'inclure que les nombres pairs.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 10);
const evenNumbers = numbers.filter(async (num) => {
await new Promise(resolve => setTimeout(resolve, 100));
return num % 2 === 0;
});
for await (const evenNumber of evenNumbers) {
console.log(evenNumber);
}
}
processNumbers();
Cas d'usage réel : Traiter un flux d'entrées de journal et filtrer les entrées en fonction de leur niveau de gravité. Par exemple, ne traiter que les erreurs et les avertissements.
async function* readLogFile(filePath) {
// Simule la lecture asynchrone d'un fichier journal ligne par ligne
const logEntries = [
{ timestamp: '...', level: 'INFO', message: '...' },
{ timestamp: '...', level: 'ERROR', message: '...' },
{ timestamp: '...', level: 'WARNING', message: '...' },
{ timestamp: '...', level: 'INFO', message: '...' },
{ timestamp: '...', level: 'ERROR', message: '...' }
];
for (const entry of logEntries) {
await new Promise(resolve => setTimeout(resolve, 50));
yield entry;
}
}
async function processLogs() {
const logEntries = readLogFile('path/to/log/file.log');
const errorAndWarningLogs = logEntries.filter(async (entry) => {
return entry.level === 'ERROR' || entry.level === 'WARNING';
});
for await (const log of errorAndWarningLogs) {
console.log(log);
}
}
3. `take()`
L'aide take() crée un nouveau générateur asynchrone qui ne produit que les n premières valeurs de la séquence originale. C'est utile pour limiter le nombre d'éléments traités à partir d'un flux potentiellement infini ou très volumineux.
Syntaxe :
asyncGenerator.take(n)
Exemple : Prendre les 3 premiers nombres d'un flux de nombres.
async function* generateNumbers(start) {
let i = start;
while (true) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i++;
}
}
async function processNumbers() {
const numbers = generateNumbers(1);
const firstThree = numbers.take(3);
for await (const num of firstThree) {
console.log(num);
}
}
processNumbers();
Cas d'usage réel : Afficher les 5 premiers résultats d'une recherche à partir d'une API de recherche asynchrone.
async function* search(query) {
// Simule la récupération des résultats de recherche depuis une API
const results = [
{ title: 'Result 1', url: '...' },
{ title: 'Result 2', url: '...' },
{ title: 'Result 3', url: '...' },
{ title: 'Result 4', url: '...' },
{ title: 'Result 5', url: '...' },
{ title: 'Result 6', url: '...' }
];
for (const result of results) {
await new Promise(resolve => setTimeout(resolve, 100));
yield result;
}
}
async function displayTopSearchResults(query) {
const searchResults = search(query);
const top5Results = searchResults.take(5);
for await (const result of top5Results) {
console.log(result);
}
}
4. `drop()`
L'aide drop() crée un nouveau générateur asynchrone qui ignore les n premières valeurs de la séquence originale et produit les valeurs restantes. C'est l'opposé de take() et est utile pour ignorer les parties initiales d'un flux.
Syntaxe :
asyncGenerator.drop(n)
Exemple : Ignorer les 2 premiers nombres d'un flux de nombres.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
const remainingNumbers = numbers.drop(2);
for await (const num of remainingNumbers) {
console.log(num);
}
}
processNumbers();
Cas d'usage réel : Paginer à travers un grand ensemble de données récupéré d'une API, en ignorant les résultats déjà affichés.
async function* fetchData(url, pageSize, pageNumber) {
const offset = (pageNumber - 1) * pageSize;
// Simule la récupération de données avec un décalage
const data = [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' },
{ id: 3, name: 'Item 3' },
{ id: 4, name: 'Item 4' },
{ id: 5, name: 'Item 5' },
{ id: 6, name: 'Item 6' },
{ id: 7, name: 'Item 7' },
{ id: 8, name: 'Item 8' }
];
const pageData = data.slice(offset, offset + pageSize);
for (const item of pageData) {
await new Promise(resolve => setTimeout(resolve, 100));
yield item;
}
}
async function displayPage(pageNumber) {
const pageSize = 3;
const allData = fetchData('api/data', pageSize, pageNumber);
const page = allData.drop((pageNumber - 1) * pageSize); // ignore les éléments des pages précédentes
const results = page.take(pageSize);
for await (const item of results) {
console.log(item);
}
}
// Exemple d'utilisation
displayPage(2);
5. `flatMap()`
L'aide flatMap() transforme chaque valeur de la séquence asynchrone en appliquant une fonction qui retourne un itérable asynchrone. Elle aplatit ensuite l'itérable asynchrone résultant en un seul générateur asynchrone. C'est utile pour transformer chaque valeur en un flux de valeurs, puis combiner ces flux.
Syntaxe :
asyncGenerator.flatMap(callback)
Exemple : Transformer un flux de phrases en un flux de mots.
async function* generateSentences() {
const sentences = [
'This is the first sentence.',
'This is the second sentence.',
'This is the third sentence.'
];
for (const sentence of sentences) {
await new Promise(resolve => setTimeout(resolve, 200));
yield sentence;
}
}
async function* stringToWords(sentence) {
const words = sentence.split(' ');
for (const word of words) {
await new Promise(resolve => setTimeout(resolve, 50));
yield word;
}
}
async function processSentences() {
const sentences = generateSentences();
const words = sentences.flatMap(async (sentence) => {
return stringToWords(sentence);
});
for await (const word of words) {
console.log(word);
}
}
processSentences();
Cas d'usage réel : Récupérer les commentaires de plusieurs articles de blog et les combiner en un seul flux pour traitement.
async function* fetchBlogPostIds() {
const blogPostIds = [1, 2, 3]; // Simule la récupération des ID d'articles de blog depuis une API
for (const id of blogPostIds) {
await new Promise(resolve => setTimeout(resolve, 100));
yield id;
}
}
async function* fetchCommentsForPost(postId) {
// Simule la récupération des commentaires pour un article de blog depuis une API
const comments = [
{ postId: postId, text: `Comment 1 for post ${postId}` },
{ postId: postId, text: `Comment 2 for post ${postId}` }
];
for (const comment of comments) {
await new Promise(resolve => setTimeout(resolve, 50));
yield comment;
}
}
async function processComments() {
const postIds = fetchBlogPostIds();
const allComments = postIds.flatMap(async (postId) => {
return fetchCommentsForPost(postId);
});
for await (const comment of allComments) {
console.log(comment);
}
}
6. `reduce()`
L'aide reduce() applique une fonction à un accumulateur et à chaque valeur du générateur asynchrone (de gauche à droite) pour le réduire à une seule valeur. C'est utile pour agréger des données à partir d'un flux asynchrone.
Syntaxe :
asyncGenerator.reduce(callback, initialValue)
Exemple : Calculer la somme des nombres dans un flux.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
const sum = await numbers.reduce(async (accumulator, num) => {
await new Promise(resolve => setTimeout(resolve, 100));
return accumulator + num;
}, 0);
console.log('Sum:', sum);
}
processNumbers();
Cas d'usage réel : Calculer le temps de réponse moyen d'une série d'appels API.
async function* fetchResponseTimes(apiEndpoints) {
for (const endpoint of apiEndpoints) {
const startTime = Date.now();
try {
await fetch(endpoint);
const endTime = Date.now();
const responseTime = endTime - startTime;
await new Promise(resolve => setTimeout(resolve, 50));
yield responseTime;
} catch (error) {
console.error(`Error fetching ${endpoint}: ${error}`);
yield 0; // Ou gérer l'erreur de manière appropriée
}
}
}
async function calculateAverageResponseTime() {
const apiEndpoints = [
'https://api.example.com/endpoint1',
'https://api.example.com/endpoint2',
'https://api.example.com/endpoint3'
];
const responseTimes = fetchResponseTimes(apiEndpoints);
let count = 0;
const sum = await responseTimes.reduce(async (accumulator, time) => {
count++;
return accumulator + time;
}, 0);
const average = count > 0 ? sum / count : 0;
console.log(`Average response time: ${average} ms`);
}
7. `toArray()`
L'aide toArray() consomme le générateur asynchrone et retourne une promesse qui se résout en un tableau contenant toutes les valeurs produites par le générateur. C'est utile lorsque vous avez besoin de collecter toutes les valeurs du flux dans un seul tableau pour un traitement ultérieur.
Syntaxe :
asyncGenerator.toArray()
Exemple : Collecter des nombres d'un flux dans un tableau.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
const numberArray = await numbers.toArray();
console.log('Number Array:', numberArray);
}
processNumbers();
Cas d'usage réel : Collecter tous les éléments d'une API paginée dans un seul tableau pour un filtrage ou un tri côté client.
async function* fetchAllItems(apiEndpoint) {
let pageNumber = 1;
const pageSize = 100; // Ajuster en fonction des limites de pagination de l'API
while (true) {
const url = `${apiEndpoint}?page=${pageNumber}&pageSize=${pageSize}`;
const response = await fetch(url);
const data = await response.json();
if (!data || data.length === 0) {
break; // Plus de données
}
for (const item of data) {
await new Promise(resolve => setTimeout(resolve, 50));
yield item;
}
pageNumber++;
}
}
async function processAllItems() {
const apiEndpoint = 'https://api.example.com/items';
const allItems = fetchAllItems(apiEndpoint);
const itemsArray = await allItems.toArray();
console.log(`Fetched ${itemsArray.length} items.`);
// Un traitement ultérieur peut être effectué sur `itemsArray`
}
8. `forEach()`
L'aide forEach() exécute une fonction fournie une fois pour chaque valeur du générateur asynchrone. Contrairement aux autres aides, forEach() ne retourne pas de nouveau générateur asynchrone ; elle est utilisée pour effectuer des effets de bord sur chaque valeur.
Syntaxe :
asyncGenerator.forEach(callback)
Exemple : Journaliser chaque nombre d'un flux dans la console.
async function* generateNumbers(start, end) {
for (let i = start; i <= end; i++) {
await new Promise(resolve => setTimeout(resolve, 200));
yield i;
}
}
async function processNumbers() {
const numbers = generateNumbers(1, 5);
await numbers.forEach(async (num) => {
await new Promise(resolve => setTimeout(resolve, 100));
console.log('Number:', num);
});
}
processNumbers();
Cas d'usage réel : Envoyer des mises à jour en temps réel à une interface utilisateur à mesure que les données sont traitées à partir d'un flux.
async function* fetchRealTimeData(dataSource) {
//Simuler la récupération de données en temps réel (par ex. cours de la bourse).
const dataStream = [
{ timestamp: new Date(), price: 100 },
{ timestamp: new Date(), price: 101 },
{ timestamp: new Date(), price: 102 }
];
for (const dataPoint of dataStream) {
await new Promise(resolve => setTimeout(resolve, 500));
yield dataPoint;
}
}
async function updateUI() {
const realTimeData = fetchRealTimeData('stock-api');
await realTimeData.forEach(async (data) => {
//Simuler la mise Ă jour de l'UI
await new Promise(resolve => setTimeout(resolve, 100));
console.log(`Updating UI with data: ${JSON.stringify(data)}`);
// Le code pour mettre à jour réellement l'UI irait ici.
});
}
Combiner les Aides pour Générateurs Asynchrones pour des Pipelines de Données Complexes
La véritable puissance des aides pour générateurs asynchrones réside dans leur capacité à être enchaînées pour créer des pipelines de données complexes. Cela vous permet d'effectuer plusieurs transformations et opérations sur un flux asynchrone de manière concise et lisible.
Exemple : Filtrer un flux de nombres pour n'inclure que les nombres pairs, puis les mettre au carré, et enfin prendre les 3 premiers résultats.
async function* generateNumbers(start) {
let i = start;
while (true) {
await new Promise(resolve => setTimeout(resolve, 100));
yield i++;
}
}
async function processNumbers() {
const numbers = generateNumbers(1);
const processedNumbers = numbers
.filter(async (num) => num % 2 === 0)
.map(async (num) => num * num)
.take(3);
for await (const num of processedNumbers) {
console.log(num);
}
}
processNumbers();
Cas d'usage réel : Récupérer des données utilisateur, filtrer les utilisateurs en fonction de leur emplacement, transformer leurs données pour n'inclure que les champs pertinents, puis afficher les 10 premiers utilisateurs sur une carte.
async function* fetchUsers() {
// Simuler la récupération des utilisateurs d'une base de données ou d'une API
const users = [
{ id: 1, name: 'John Doe', location: 'New York', email: 'john.doe@example.com' },
{ id: 2, name: 'Jane Smith', location: 'London', email: 'jane.smith@example.com' },
{ id: 3, name: 'Ken Tan', location: 'Singapore', email: 'ken.tan@example.com' },
{ id: 4, name: 'Alice Jones', location: 'New York', email: 'alice.jones@example.com' },
{ id: 5, name: 'Bob Williams', location: 'London', email: 'bob.williams@example.com' },
{ id: 6, name: 'Siti Rahman', location: 'Singapore', email: 'siti.rahman@example.com' },
{ id: 7, name: 'Ahmed Khan', location: 'Dubai', email: 'ahmed.khan@example.com' },
{ id: 8, name: 'Maria Garcia', location: 'Madrid', email: 'maria.garcia@example.com' },
{ id: 9, name: 'Li Wei', location: 'Shanghai', email: 'li.wei@example.com' },
{ id: 10, name: 'Hans MĂĽller', location: 'Berlin', email: 'hans.muller@example.com' },
{ id: 11, name: 'Emily Chen', location: 'Sydney', email: 'emily.chen@example.com' }
];
for (const user of users) {
await new Promise(resolve => setTimeout(resolve, 50));
yield user;
}
}
async function displayUsersOnMap(location, maxUsers) {
const users = fetchUsers();
const usersForMap = users
.filter(async (user) => user.location === location)
.map(async (user) => ({
id: user.id,
name: user.name,
location: user.location
}))
.take(maxUsers);
console.log(`Displaying up to ${maxUsers} users from ${location} on the map:`);
for await (const user of usersForMap) {
console.log(user);
}
}
// Exemples d'utilisation :
displayUsersOnMap('New York', 2);
displayUsersOnMap('London', 5);
Polyfills et Support des Navigateurs
Le support des aides pour générateurs asynchrones peut varier en fonction de l'environnement JavaScript. Si vous devez prendre en charge des navigateurs ou des environnements plus anciens, vous devrez peut-être utiliser des polyfills. Un polyfill fournit la fonctionnalité manquante en l'implémentant en JavaScript. Plusieurs bibliothèques de polyfills sont disponibles pour les aides pour générateurs asynchrones, comme core-js.
Exemple avec core-js :
// Importer les polyfills nécessaires
require('core-js/features/async-iterator/map');
require('core-js/features/async-iterator/filter');
// ... importer d'autres aides nécessaires
Gestion des Erreurs
Lorsque vous travaillez avec des opérations asynchrones, il est crucial de gérer correctement les erreurs. Avec les aides pour générateurs asynchrones, la gestion des erreurs peut être effectuée à l'aide de blocs try...catch au sein des fonctions asynchrones utilisées dans les aides.
Exemple : Gérer les erreurs lors de la récupération de données dans une opération map().
async function* fetchData(urls) {
for (const url of urls) {
try {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const data = await response.json();
yield data;
} catch (error) {
console.error(`Error fetching data from ${url}: ${error}`);
yield null; // Ou gérer l'erreur de manière appropriée, par ex. en produisant un objet d'erreur
}
}
}
async function processData() {
const urls = [
'https://api.example.com/data1',
'https://api.example.com/data2',
'https://api.example.com/data3'
];
const dataStream = fetchData(urls);
const processedData = dataStream.map(async (data) => {
if (data === null) {
return null; // Propager l'erreur
}
// Traiter les données
return data;
});
for await (const item of processedData) {
if (item === null) {
console.log('Skipping item due to error');
continue;
}
console.log('Processed Item:', item);
}
}
processData();
Bonnes Pratiques et Considérations
- Évaluation Paresseuse : Les aides pour générateurs asynchrones sont évaluées de manière paresseuse, ce qui signifie qu'elles ne traitent les données que lorsqu'elles sont demandées. Cela peut améliorer les performances, en particulier lorsqu'il s'agit de grands ensembles de données.
- Gestion des Erreurs : Gérez toujours correctement les erreurs au sein des fonctions asynchrones utilisées dans les aides.
- Polyfills : Utilisez des polyfills si nécessaire pour prendre en charge les navigateurs ou environnements plus anciens.
- Lisibilité : Utilisez des noms de variables et des commentaires descriptifs pour rendre votre code plus lisible et maintenable.
- Performance : Soyez conscient des implications sur les performances de l'enchaînement de plusieurs aides. Bien que l'évaluation paresseuse aide, un enchaînement excessif peut toujours introduire une surcharge.
Conclusion
Les aides pour générateurs asynchrones JavaScript offrent un moyen puissant et élégant de créer, transformer et gérer les flux de données asynchrones. En tirant parti de ces aides, les développeurs peuvent écrire un code plus concis, lisible et maintenable pour gérer des opérations asynchrones complexes. Comprendre les principes fondamentaux des générateurs et itérateurs asynchrones, ainsi que les fonctionnalités de chaque aide, est essentiel pour utiliser efficacement ces outils dans des applications réelles. Que vous construisiez des pipelines de données, traitiez des données en temps réel ou gériez des réponses d'API asynchrones, les aides pour générateurs asynchrones peuvent simplifier considérablement votre code et améliorer son efficacité globale.